Data Science Assignment¶

A. Importarea bibliotecilor¶

In [1]:
import numpy as np 
import pandas as pd 
import matplotlib.pyplot as plt 
import seaborn as sns 
import missingno as msno 
from sklearn.model_selection import train_test_split
from sklearn.ensemble import RandomForestClassifier
from xgboost import XGBClassifier
from sklearn.metrics import accuracy_score, precision_score, recall_score, roc_auc_score, confusion_matrix, roc_curve
import shap 
import pickle

B. Citirea setului de date¶

In [2]:
path = 'C:\\Users\\valen\\Desktop\\data\\dataset.csv'
In [3]:
data = pd.read_csv(path)

C. Vizualizare, prelucrarea si analiza setului de date¶

1. Previzualizarea datelor si verificarea dimensiunii acestora¶

a) Dimensiune:

In [4]:
data.shape
Out[4]:
(10127, 21)

b) Previzualizarea datelor:

In [5]:
#folosesc functia head()
data.head(20)
Out[5]:
CLIENTNUM Attrition_Flag Customer_Age Gender Dependent_count Education_Level Marital_Status Income_Category Card_Category Months_on_book ... Months_Inactive_12_mon Contacts_Count_12_mon Credit_Limit Total_Used_Bal Total_Unused_Bal Total_Amt_Chng_Q4_Q1 Total_Trans_Amt Total_Trans_Ct Total_Ct_Chng_Q4_Q1 Avg_Utilization_Ratio
0 768805383 Existing Customer 45.0 M 3.0 High School Married $60K - $80K Blue 39 ... 1 3 12691.0 777 11914.0 1.335 1144 42 1.625 0.061
1 818770008 Existing Customer 49.0 F 5.0 Graduate Single Less than $40K Blue 44 ... 1 2 8256.0 864 7392.0 1.541 1291 33 3.714 0.105
2 713982108 Existing Customer 51.0 M 3.0 Graduate Married $80K - $120K Blue 36 ... 1 0 3418.0 0 3418.0 2.594 1887 20 2.333 0.000
3 769911858 Existing Customer 40.0 F 4.0 High School NaN Less than $40K Blue 34 ... 4 1 3313.0 2517 796.0 1.405 1171 20 2.333 0.760
4 709106358 Existing Customer 40.0 M 3.0 Uneducated Married $60K - $80K Blue 21 ... 1 0 4716.0 0 4716.0 2.175 816 28 2.500 0.000
5 713061558 Existing Customer 44.0 M 2.0 Graduate Married $40K - $60K Blue 36 ... 1 2 4010.0 1247 2763.0 1.376 1088 24 0.846 0.311
6 810347208 Existing Customer 51.0 M 4.0 NaN Married $120K + Gold 46 ... 1 3 34516.0 2264 32252.0 1.975 1330 31 0.722 0.066
7 818906208 Existing Customer 32.0 M 0.0 High School NaN $60K - $80K Silver 27 ... 2 2 29081.0 1396 27685.0 2.204 1538 36 0.714 0.048
8 710930508 Existing Customer 37.0 M 3.0 Uneducated Single $60K - $80K Blue 36 ... 2 0 22352.0 2517 19835.0 3.355 1350 24 1.182 0.113
9 719661558 Existing Customer 48.0 M 2.0 Graduate Single $80K - $120K Blue 36 ... 3 3 11656.0 1677 9979.0 1.524 1441 32 0.882 0.144
10 708790833 Existing Customer 42.0 M 5.0 Uneducated NaN $120K + Blue 31 ... 3 2 6748.0 1467 5281.0 0.831 1201 42 0.680 0.217
11 710821833 Existing Customer 65.0 M 1.0 NaN Married $40K - $60K Blue 54 ... 2 3 9095.0 1587 7508.0 1.433 1314 26 1.364 0.174
12 710599683 Existing Customer 56.0 M 1.0 College Single $80K - $120K Blue 36 ... 6 0 11751.0 0 11751.0 3.397 1539 17 3.250 0.000
13 816082233 Existing Customer 35.0 M 3.0 Graduate NaN $60K - $80K Blue 30 ... 1 3 8547.0 1666 6881.0 1.163 1311 33 2.000 0.195
14 712396908 Existing Customer 57.0 F 2.0 Graduate Married Less than $40K Blue 48 ... 2 2 2436.0 680 1756.0 1.190 1570 29 0.611 0.279
15 714885258 Existing Customer 44.0 M 4.0 NaN NaN $80K - $120K Blue 37 ... 1 2 4234.0 972 3262.0 1.707 1348 27 1.700 0.230
16 709967358 Existing Customer 48.0 M 4.0 Post-Graduate Single $80K - $120K Blue 36 ... 2 3 30367.0 2362 28005.0 1.708 1671 27 0.929 0.078
17 753327333 Existing Customer 41.0 M 3.0 NaN Married $80K - $120K Blue 34 ... 4 1 13535.0 1291 12244.0 0.653 1028 21 1.625 0.095
18 806160108 Existing Customer 61.0 M 1.0 High School Married $40K - $60K Blue 56 ... 2 3 3193.0 2517 676.0 1.831 1336 30 1.143 0.788
19 709327383 Existing Customer 45.0 F 2.0 Graduate Married abc Blue 37 ... 1 2 14470.0 1157 13313.0 0.966 1207 21 0.909 0.080

20 rows × 21 columns

c) Afișarea coloanelor și a tipurilor de date din acestea.

In [6]:
coloane = data.columns
print(coloane)
Index(['CLIENTNUM', 'Attrition_Flag', 'Customer_Age', 'Gender',
       'Dependent_count', 'Education_Level', 'Marital_Status',
       'Income_Category', 'Card_Category', 'Months_on_book',
       'Total_Relationship_Count', 'Months_Inactive_12_mon',
       'Contacts_Count_12_mon', 'Credit_Limit', 'Total_Used_Bal',
       'Total_Unused_Bal', 'Total_Amt_Chng_Q4_Q1', 'Total_Trans_Amt',
       'Total_Trans_Ct', 'Total_Ct_Chng_Q4_Q1', 'Avg_Utilization_Ratio'],
      dtype='object')
In [7]:
data.dtypes
Out[7]:
CLIENTNUM                     int64
Attrition_Flag               object
Customer_Age                float64
Gender                       object
Dependent_count             float64
Education_Level              object
Marital_Status               object
Income_Category              object
Card_Category                object
Months_on_book                int64
Total_Relationship_Count      int64
Months_Inactive_12_mon        int64
Contacts_Count_12_mon         int64
Credit_Limit                float64
Total_Used_Bal                int64
Total_Unused_Bal            float64
Total_Amt_Chng_Q4_Q1        float64
Total_Trans_Amt               int64
Total_Trans_Ct                int64
Total_Ct_Chng_Q4_Q1         float64
Avg_Utilization_Ratio       float64
dtype: object

Se poate observa că există 21 de coloane care furnizează diverse informații despre un client bancar, cum ar fi vârsta, venitul, nivelul de educație, starea civilă, etc. Datele întâlnite în aceste coloane pot fi sub formă de float, int sau object.

d) Aflarea informațiilor setului de date

In [8]:
#folosesc functia info()
data.info()
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 10127 entries, 0 to 10126
Data columns (total 21 columns):
 #   Column                    Non-Null Count  Dtype  
---  ------                    --------------  -----  
 0   CLIENTNUM                 10127 non-null  int64  
 1   Attrition_Flag            10127 non-null  object 
 2   Customer_Age              10124 non-null  float64
 3   Gender                    10127 non-null  object 
 4   Dependent_count           10122 non-null  float64
 5   Education_Level           8608 non-null   object 
 6   Marital_Status            9378 non-null   object 
 7   Income_Category           10125 non-null  object 
 8   Card_Category             10127 non-null  object 
 9   Months_on_book            10127 non-null  int64  
 10  Total_Relationship_Count  10127 non-null  int64  
 11  Months_Inactive_12_mon    10127 non-null  int64  
 12  Contacts_Count_12_mon     10127 non-null  int64  
 13  Credit_Limit              10127 non-null  float64
 14  Total_Used_Bal            10127 non-null  int64  
 15  Total_Unused_Bal          10127 non-null  float64
 16  Total_Amt_Chng_Q4_Q1      10127 non-null  float64
 17  Total_Trans_Amt           10127 non-null  int64  
 18  Total_Trans_Ct            10127 non-null  int64  
 19  Total_Ct_Chng_Q4_Q1       10127 non-null  float64
 20  Avg_Utilization_Ratio     10127 non-null  float64
dtypes: float64(7), int64(8), object(6)
memory usage: 1.6+ MB

e) Vizualizarea proprietăților statistice ale setului de date

In [9]:
data.describe()
Out[9]:
CLIENTNUM Customer_Age Dependent_count Months_on_book Total_Relationship_Count Months_Inactive_12_mon Contacts_Count_12_mon Credit_Limit Total_Used_Bal Total_Unused_Bal Total_Amt_Chng_Q4_Q1 Total_Trans_Amt Total_Trans_Ct Total_Ct_Chng_Q4_Q1 Avg_Utilization_Ratio
count 1.012700e+04 10124.000000 10122.000000 10127.000000 10127.000000 10127.000000 10127.000000 10127.000000 10127.000000 10127.000000 10127.000000 10127.000000 10127.000000 10127.000000 10127.000000
mean 7.391776e+08 46.326057 2.345781 35.928409 3.812580 2.341167 2.455317 8631.953698 1162.814061 7469.139637 0.759941 4404.086304 64.858695 0.712222 0.274894
std 3.690378e+07 8.017889 1.298908 7.986416 1.554408 1.010622 1.106225 9088.776650 814.987335 9090.685324 0.219207 3397.129254 23.472570 0.238086 0.275691
min 7.080821e+08 26.000000 0.000000 13.000000 1.000000 0.000000 0.000000 1438.300000 0.000000 3.000000 0.000000 510.000000 10.000000 0.000000 0.000000
25% 7.130368e+08 41.000000 1.000000 31.000000 3.000000 2.000000 2.000000 2555.000000 359.000000 1324.500000 0.631000 2155.500000 45.000000 0.582000 0.023000
50% 7.179264e+08 46.000000 2.000000 36.000000 4.000000 2.000000 2.000000 4549.000000 1276.000000 3474.000000 0.736000 3899.000000 67.000000 0.702000 0.176000
75% 7.731435e+08 52.000000 3.000000 40.000000 5.000000 3.000000 3.000000 11067.500000 1784.000000 9859.000000 0.859000 4741.000000 81.000000 0.818000 0.503000
max 8.283431e+08 73.000000 5.000000 56.000000 6.000000 6.000000 6.000000 34516.000000 2517.000000 34516.000000 3.397000 18484.000000 139.000000 3.714000 0.999000
In [10]:
data.describe(include = ['object'])
Out[10]:
Attrition_Flag Gender Education_Level Marital_Status Income_Category Card_Category
count 10127 10127 8608 9378 10125 10127
unique 2 2 6 3 6 4
top Existing Customer F Graduate Married Less than $40K Blue
freq 8500 5358 3128 4687 3560 9436
In [11]:
data.describe(include = ['int64'])
Out[11]:
CLIENTNUM Months_on_book Total_Relationship_Count Months_Inactive_12_mon Contacts_Count_12_mon Total_Used_Bal Total_Trans_Amt Total_Trans_Ct
count 1.012700e+04 10127.000000 10127.000000 10127.000000 10127.000000 10127.000000 10127.000000 10127.000000
mean 7.391776e+08 35.928409 3.812580 2.341167 2.455317 1162.814061 4404.086304 64.858695
std 3.690378e+07 7.986416 1.554408 1.010622 1.106225 814.987335 3397.129254 23.472570
min 7.080821e+08 13.000000 1.000000 0.000000 0.000000 0.000000 510.000000 10.000000
25% 7.130368e+08 31.000000 3.000000 2.000000 2.000000 359.000000 2155.500000 45.000000
50% 7.179264e+08 36.000000 4.000000 2.000000 2.000000 1276.000000 3899.000000 67.000000
75% 7.731435e+08 40.000000 5.000000 3.000000 3.000000 1784.000000 4741.000000 81.000000
max 8.283431e+08 56.000000 6.000000 6.000000 6.000000 2517.000000 18484.000000 139.000000
In [12]:
data.describe(include = ['float64'])
Out[12]:
Customer_Age Dependent_count Credit_Limit Total_Unused_Bal Total_Amt_Chng_Q4_Q1 Total_Ct_Chng_Q4_Q1 Avg_Utilization_Ratio
count 10124.000000 10122.000000 10127.000000 10127.000000 10127.000000 10127.000000 10127.000000
mean 46.326057 2.345781 8631.953698 7469.139637 0.759941 0.712222 0.274894
std 8.017889 1.298908 9088.776650 9090.685324 0.219207 0.238086 0.275691
min 26.000000 0.000000 1438.300000 3.000000 0.000000 0.000000 0.000000
25% 41.000000 1.000000 2555.000000 1324.500000 0.631000 0.582000 0.023000
50% 46.000000 2.000000 4549.000000 3474.000000 0.736000 0.702000 0.176000
75% 52.000000 3.000000 11067.500000 9859.000000 0.859000 0.818000 0.503000
max 73.000000 5.000000 34516.000000 34516.000000 3.397000 3.714000 0.999000

2. Analiza univariată realizată pe baza variabilei target¶

Variabila target va fi reprezentată de elementele din coloana "Attrition_Flag". Această coloană conține două valori: "Existing Customer" și "Attrited Customer". În cazul în care valoarea din coloana este "Existing Customer", înseamnă că acel client este activ și nu a părăsit banca. În schimb, dacă întâlnim valoarea "Attrited Customer", acest lucru indică un client care a renunțat la bancă. Putem utiliza această coloană pentru a evalua câți clienți ar putea părăsi banca și pentru a estima riscul asociat cu această situație.

a) Verificăm dacă variabila are valori nule

In [13]:
data['Attrition_Flag'].isnull().sum()
Out[13]:
0

Lipsa valorilor nule indică faptul că fiecare client este fie activ, fie a părăsit sistemul băncii.

b) Verificăm câte valori unice avem și care sunt acestea

In [14]:
data['Attrition_Flag'].nunique()
Out[14]:
2
In [15]:
data['Attrition_Flag'].unique()
Out[15]:
array(['Existing Customer', 'Attrited Customer'], dtype=object)

c) Verificăm frecvența valorilor și procentajul acestora

In [16]:
data['Attrition_Flag'].value_counts()
Out[16]:
Existing Customer    8500
Attrited Customer    1627
Name: Attrition_Flag, dtype: int64

Observăm că 8500 de clienți sunt încă în sistemul băncii noastre, iar 1627 l-au părăsit.

In [17]:
data['Attrition_Flag'].value_counts()/len(data)*100
Out[17]:
Existing Customer    83.934038
Attrited Customer    16.065962
Name: Attrition_Flag, dtype: float64

Observăm că 83.93% din clienți au ales să nu părăsească banca, pe când 16.06% au făcut acest lucru.

d) Vizualizăm distribuția în variabila "Attrition Flag" printr-un grafic

In [18]:
fig,ax = plt.subplots(figsize = (8,6))
ax = sns.countplot(data = data, x = 'Attrition_Flag',palette = 'Set2')
plt.show()

Conform graficului, observăm că 8500 de clienți sunt încă în sistemul băncii noastre, pe când 1627 l-au părăsit.

e) Convertim valorile din variabila target "Attrition_Flag" din 'object' în 'int'

In [19]:
data['Attrition_Flag'] = data['Attrition_Flag'].map({'Attrited Customer':0,'Existing Customer':1})
In [20]:
data['Attrition_Flag'].value_counts()
Out[20]:
1    8500
0    1627
Name: Attrition_Flag, dtype: int64

3. Analiza caracteristicilor¶

a) Explorarea variabilelor categorice

In [22]:
#Gasim variabilele categorice
data.dtypes
Out[22]:
CLIENTNUM                     int64
Attrition_Flag                int64
Customer_Age                float64
Gender                       object
Dependent_count             float64
Education_Level              object
Marital_Status               object
Income_Category              object
Card_Category                object
Months_on_book                int64
Total_Relationship_Count      int64
Months_Inactive_12_mon        int64
Contacts_Count_12_mon         int64
Credit_Limit                float64
Total_Used_Bal                int64
Total_Unused_Bal            float64
Total_Amt_Chng_Q4_Q1        float64
Total_Trans_Amt               int64
Total_Trans_Ct                int64
Total_Ct_Chng_Q4_Q1         float64
Avg_Utilization_Ratio       float64
dtype: object

Variabilele categorice sunt cele de tip object, asadar pe acestea le selectam

In [23]:
categorical_variables = [col for col in data.columns if data[col].dtypes=='object']
In [24]:
print('In tabel exista',len(categorical_variables),'si acestea sunt:',categorical_variables)
In tabel exista 5 si acestea sunt: ['Gender', 'Education_Level', 'Marital_Status', 'Income_Category', 'Card_Category']

Se va distribui fiecare valoare categorică pe un grafic in raport cu variabila target.

In [25]:
for col in categorical_variables:
    fig, ax = plt.subplots(figsize = (8,7))
    ax = sns.countplot(data = data, x = col, hue = 'Attrition_Flag')
    plt.show()

Observații: Din punct de vedere al sexului, categoria cu cei mai mulți clienți o reprezintă persoanele de sex feminin. În cazul nivelului de educație, cei mai mulți clienți au absolvit facultatea. Având în vedere starea civilă, cei mai mulți clienți sunt căsătoriți. În ceea ce privește categoria de venit, cei mai mulți clienți au un salariu mai mic de $40K anual. În ceea ce privește categoria de card, cei mai mulți clienți au optat pentru varianta "Blue".

b) Verificăm valorile lipsă din cadrul variabilelor categorice

In [26]:
data[categorical_variables].isnull().sum()
Out[26]:
Gender                0
Education_Level    1519
Marital_Status      749
Income_Category       2
Card_Category         0
dtype: int64

Observăm că pentru nivelul de educație nu avem date pentru 1519 clienți, pentru starea civilă datele nu sunt disponibile pentru 749 de clienți, iar datele referitoare la categoria de venit lipsesc pentru 2 clienți.

In [27]:
msno.bar(data[categorical_variables])
Out[27]:
<Axes: >

Acest grafic ne ajută să vizualizăm valorile lipsă din cadrul variabilelor.

In [28]:
data[categorical_variables].value_counts()
Out[28]:
Gender  Education_Level  Marital_Status  Income_Category  Card_Category
F       Graduate         Married         Less than $40K   Blue             467
                         Single          Less than $40K   Blue             382
        High School      Married         Less than $40K   Blue             280
                         Single          Less than $40K   Blue             228
M       Graduate         Married         $80K - $120K     Blue             221
                                                                          ... 
F       Post-Graduate    Married         $40K - $60K      Silver             1
                         Single          $40K - $60K      Silver             1
                                         Less than $40K   Silver             1
                                         abc              Gold               1
M       Post-Graduate    Divorced        $40K - $60K      Blue               1
Length: 317, dtype: int64

Completam datele necunoscute

In [29]:
data['Education_Level'] = data['Education_Level'].fillna('Unknown')
data['Marital_Status'] = data['Marital_Status'].fillna('Unknown')
data['Income_Category'] = data['Income_Category'].fillna('Unknown')
In [30]:
data[categorical_variables].isnull()
Out[30]:
Gender Education_Level Marital_Status Income_Category Card_Category
0 False False False False False
1 False False False False False
2 False False False False False
3 False False False False False
4 False False False False False
... ... ... ... ... ...
10122 False False False False False
10123 False False False False False
10124 False False False False False
10125 False False False False False
10126 False False False False False

10127 rows × 5 columns

In [31]:
data[categorical_variables].isnull().sum()
Out[31]:
Gender             0
Education_Level    0
Marital_Status     0
Income_Category    0
Card_Category      0
dtype: int64

c) Variația valorilor categorice

In [32]:
data[categorical_variables].nunique()
Out[32]:
Gender             2
Education_Level    7
Marital_Status     4
Income_Category    7
Card_Category      4
dtype: int64

Observăm că nu există variabile cu o singură valoare, așadar nu modificăm categorical_variables.

d) Convertirea variabilelor

In [33]:
data['Gender'].value_counts()/len(data)*100
Out[33]:
F    52.908068
M    47.091932
Name: Gender, dtype: float64
In [34]:
data['Education_Level'].value_counts()/len(data)*100
Out[34]:
Graduate         30.887726
High School      19.877555
Unknown          14.999506
Uneducated       14.683519
College          10.002962
Post-Graduate     5.095290
Doctorate         4.453441
Name: Education_Level, dtype: float64
In [35]:
data['Marital_Status'].value_counts()/len(data)*100
Out[35]:
Married     46.282216
Single      38.935519
Unknown      7.396070
Divorced     7.386195
Name: Marital_Status, dtype: float64
In [36]:
data['Income_Category'].value_counts()/len(data)*100
Out[36]:
Less than $40K    35.153550
$40K - $60K       17.665646
$80K - $120K      15.157500
$60K - $80K       13.844179
abc               10.980547
$120K +            7.178829
Unknown            0.019749
Name: Income_Category, dtype: float64
In [37]:
data['Card_Category'].value_counts()/len(data)*100
Out[37]:
Blue        93.176656
Silver       5.480399
Gold         1.145453
Platinum     0.197492
Name: Card_Category, dtype: float64

Observăm că în ceea ce privește coloana "Income_Category", există o ramură de venit denumită "abc" care nu ne este utilă și pe care o putem muta într-un alt loc, împreună cu categoria "unknown".

Folosim encoding și adăugăm valoarea 'abc' din variabila 'Income_Category' în 'Unknown'.

In [38]:
data['Income_Category'] = np.where(data['Income_Category'] == 'abc','Unknown',data['Income_Category'])
In [39]:
data['Income_Category'].value_counts()/len(data)*100
Out[39]:
Less than $40K    35.153550
$40K - $60K       17.665646
$80K - $120K      15.157500
$60K - $80K       13.844179
Unknown           11.000296
$120K +            7.178829
Name: Income_Category, dtype: float64
In [40]:
data[categorical_variables].nunique()
Out[40]:
Gender             2
Education_Level    7
Marital_Status     4
Income_Category    6
Card_Category      4
dtype: int64

e) Explorarea variabilelor numerice

In [41]:
numerical_columns = [col for col in data.columns if data[col].dtypes != 'object']
In [42]:
print('Sunt',len(numerical_columns),'coloane numerice. Acestea sunt',numerical_columns)
Sunt 16 coloane numerice. Acestea sunt ['CLIENTNUM', 'Attrition_Flag', 'Customer_Age', 'Dependent_count', 'Months_on_book', 'Total_Relationship_Count', 'Months_Inactive_12_mon', 'Contacts_Count_12_mon', 'Credit_Limit', 'Total_Used_Bal', 'Total_Unused_Bal', 'Total_Amt_Chng_Q4_Q1', 'Total_Trans_Amt', 'Total_Trans_Ct', 'Total_Ct_Chng_Q4_Q1', 'Avg_Utilization_Ratio']
In [43]:
data[numerical_columns].head()
Out[43]:
CLIENTNUM Attrition_Flag Customer_Age Dependent_count Months_on_book Total_Relationship_Count Months_Inactive_12_mon Contacts_Count_12_mon Credit_Limit Total_Used_Bal Total_Unused_Bal Total_Amt_Chng_Q4_Q1 Total_Trans_Amt Total_Trans_Ct Total_Ct_Chng_Q4_Q1 Avg_Utilization_Ratio
0 768805383 1 45.0 3.0 39 5 1 3 12691.0 777 11914.0 1.335 1144 42 1.625 0.061
1 818770008 1 49.0 5.0 44 6 1 2 8256.0 864 7392.0 1.541 1291 33 3.714 0.105
2 713982108 1 51.0 3.0 36 4 1 0 3418.0 0 3418.0 2.594 1887 20 2.333 0.000
3 769911858 1 40.0 4.0 34 3 4 1 3313.0 2517 796.0 1.405 1171 20 2.333 0.760
4 709106358 1 40.0 3.0 21 5 1 0 4716.0 0 4716.0 2.175 816 28 2.500 0.000

Grafice

In [44]:
for col in numerical_columns:
    fig, ax = plt.subplots(figsize = (15,6))
    sns.histplot(data = data, x = col, hue = 'Attrition_Flag',bins = 100)
    plt.show()
In [45]:
data[numerical_columns].isnull().sum()
Out[45]:
CLIENTNUM                   0
Attrition_Flag              0
Customer_Age                3
Dependent_count             5
Months_on_book              0
Total_Relationship_Count    0
Months_Inactive_12_mon      0
Contacts_Count_12_mon       0
Credit_Limit                0
Total_Used_Bal              0
Total_Unused_Bal            0
Total_Amt_Chng_Q4_Q1        0
Total_Trans_Amt             0
Total_Trans_Ct              0
Total_Ct_Chng_Q4_Q1         0
Avg_Utilization_Ratio       0
dtype: int64
In [46]:
msno.bar(data[numerical_columns])
Out[46]:
<Axes: >

Prin acest grafic observăm că lipsesc 5 valori din Dependent_count și 3 valori din Customer_Age

In [47]:
data['Dependent_count']
Out[47]:
0        3.0
1        5.0
2        3.0
3        4.0
4        3.0
        ... 
10122    2.0
10123    2.0
10124    1.0
10125    2.0
10126    2.0
Name: Dependent_count, Length: 10127, dtype: float64
In [48]:
data['Dependent_count'] = data['Dependent_count'].fillna(data['Dependent_count'].min())
In [49]:
data['Customer_Age']
Out[49]:
0        45.0
1        49.0
2        51.0
3        40.0
4        40.0
         ... 
10122    50.0
10123    41.0
10124    44.0
10125    30.0
10126    43.0
Name: Customer_Age, Length: 10127, dtype: float64
In [50]:
data['Customer_Age'] = data['Customer_Age'].fillna(data['Customer_Age'].mean())
In [51]:
data['Dependent_count'].isnull().sum()
data['Customer_Age'].isnull().sum()
Out[51]:
0

Acum putem observa că nu mai există valori nule in variabilele numerice.

f) Căutăm dacă avem valori obligatorii unice pe care le putem elimina.

In [52]:
data[numerical_columns].nunique()
Out[52]:
CLIENTNUM                   10127
Attrition_Flag                  2
Customer_Age                   46
Dependent_count                 6
Months_on_book                 44
Total_Relationship_Count        6
Months_Inactive_12_mon          7
Contacts_Count_12_mon           7
Credit_Limit                 6205
Total_Used_Bal               1974
Total_Unused_Bal             6813
Total_Amt_Chng_Q4_Q1         1158
Total_Trans_Amt              5033
Total_Trans_Ct                126
Total_Ct_Chng_Q4_Q1           830
Avg_Utilization_Ratio         964
dtype: int64

Având în vedere că nu există astfel de valori, nu vom modifica tabelul final.

In [53]:
data['Credit_Limit'].value_counts()/len(data)*100
Out[53]:
34516.0    5.016293
1438.3     5.006418
9959.0     0.177743
15987.0    0.177743
23981.0    0.118495
             ...   
9183.0     0.009875
29923.0    0.009875
9551.0     0.009875
11558.0    0.009875
10388.0    0.009875
Name: Credit_Limit, Length: 6205, dtype: float64

g) Detectam valorile extreme (outliers)

In [54]:
q1 = data['Credit_Limit'].quantile(0.25)
q3 = data['Credit_Limit'].quantile(0.75)
IQR = q3 - q1
lower_limit = q1 - 3 * IQR
upper_limit = q3 + 3 * IQR
In [55]:
data[(data['Credit_Limit']<lower_limit) | (data['Credit_Limit']>upper_limit)]['Credit_Limit']
Out[55]:
Series([], Name: Credit_Limit, dtype: float64)
In [56]:
data[(data['Credit_Limit']<lower_limit) | (data['Credit_Limit']>upper_limit)]
Out[56]:
CLIENTNUM Attrition_Flag Customer_Age Gender Dependent_count Education_Level Marital_Status Income_Category Card_Category Months_on_book ... Months_Inactive_12_mon Contacts_Count_12_mon Credit_Limit Total_Used_Bal Total_Unused_Bal Total_Amt_Chng_Q4_Q1 Total_Trans_Amt Total_Trans_Ct Total_Ct_Chng_Q4_Q1 Avg_Utilization_Ratio

0 rows × 21 columns

In [57]:
fig, ax = plt.subplots(figsize = (8,6))
sns.boxplot(y = data['Credit_Limit'], color = '#50C878', whis = 3)
plt.show()

În acest caz nu avem outlier-i, deci vom căuta și în celelalte variabile.

In [58]:
for col in numerical_columns:
    fig, ax = plt.subplots(figsize = (8,6))
    sns.boxplot(y = data[col], color = '#7F00FF', whis = 3)
    plt.show()

Observăm că următoarele variabile sunt afectate de outlier-i: Total_Ct_Chng_Q4_Q1, Total_Trans_Amt, Total_Amt_Chng_Q4_Q1, Attrition_Flag. Vom înlocui valorile outlier cu limita inferioară, atunci când acestea sunt mai mici decât limita, sau cu limita superioară, dacă valorile sunt mai mari decât aceasta.

In [59]:
affected_by_outliers = ['Total_Ct_Chng_Q4_Q1','Total_Trans_Amt','Total_Amt_Chng_Q4_Q1','Attrition_Flag']
In [60]:
def censoring_outliers(dataframe,column):
    q1 = dataframe[column].quantile(0.25)
    q3 = dataframe[column].quantile(0.75)
    IQR = q3-q1
    lower_limit = q1 - 3 * IQR
    upper_limit = q3 + 3 * IQR
    dataframe[column] = np.where(dataframe[column] < lower_limit, lower_limit, np.where(dataframe[column] > upper_limit, upper_limit, dataframe[column]))
In [61]:
for variable in affected_by_outliers:
    censoring_outliers(data,variable)
In [62]:
for col in numerical_columns:
    fig, ax = plt.subplots(figsize = (8,6))
    sns.boxplot(y = data[col], color = 'pink', whis = 3)
    plt.show()

h) Corelatie

In [63]:
correlation = data[numerical_columns].corr()
In [64]:
correlation
Out[64]:
CLIENTNUM Attrition_Flag Customer_Age Dependent_count Months_on_book Total_Relationship_Count Months_Inactive_12_mon Contacts_Count_12_mon Credit_Limit Total_Used_Bal Total_Unused_Bal Total_Amt_Chng_Q4_Q1 Total_Trans_Amt Total_Trans_Ct Total_Ct_Chng_Q4_Q1 Avg_Utilization_Ratio
CLIENTNUM 1.000000 NaN 0.007561 0.007280 0.134588 0.006907 0.005729 0.005694 0.005708 0.000825 0.005633 0.019504 -0.020926 -0.002961 0.007672 0.000266
Attrition_Flag NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN
Customer_Age 0.007561 NaN 1.000000 -0.122720 0.788890 -0.010925 0.054321 -0.018468 0.002561 0.014733 0.001239 -0.068964 -0.046050 -0.067107 -0.018824 0.007023
Dependent_count 0.007280 NaN -0.122720 1.000000 -0.103872 -0.039495 -0.012156 -0.040394 0.068422 -0.003003 0.068676 -0.036795 0.033607 0.052085 0.010959 -0.037256
Months_on_book 0.134588 NaN 0.788890 -0.103872 1.000000 -0.009203 0.074164 -0.010774 0.007507 0.008623 0.006732 -0.055050 -0.037315 -0.049819 -0.018958 -0.007541
Total_Relationship_Count 0.006907 NaN -0.010925 -0.039495 -0.009203 1.000000 -0.003675 0.055203 -0.071386 0.013726 -0.072601 0.049542 -0.359299 -0.241891 0.041464 0.067663
Months_Inactive_12_mon 0.005729 NaN 0.054321 -0.012156 0.074164 -0.003675 1.000000 0.029493 -0.020394 -0.042210 -0.016605 -0.032146 -0.036493 -0.042787 -0.043911 -0.007503
Contacts_Count_12_mon 0.005694 NaN -0.018468 -0.040394 -0.010774 0.055203 0.029493 1.000000 0.020817 -0.053913 0.025646 -0.019470 -0.122814 -0.152213 -0.098280 -0.055471
Credit_Limit 0.005708 NaN 0.002561 0.068422 0.007507 -0.071386 -0.020394 0.020817 1.000000 0.042493 0.995981 0.012631 0.169344 0.075927 -0.006200 -0.482965
Total_Used_Bal 0.000825 NaN 0.014733 -0.003003 0.008623 0.013726 -0.042210 -0.053913 0.042493 1.000000 -0.047167 0.057863 0.059969 0.056060 0.095009 0.624022
Total_Unused_Bal 0.005633 NaN 0.001239 0.068676 0.006732 -0.072601 -0.016605 0.025646 0.995981 -0.047167 1.000000 0.007441 0.163932 0.070885 -0.014717 -0.538808
Total_Amt_Chng_Q4_Q1 0.019504 NaN -0.068964 -0.036795 -0.055050 0.049542 -0.032146 -0.019470 0.012631 0.057863 0.007441 1.000000 0.055264 0.024123 0.366869 0.036923
Total_Trans_Amt -0.020926 NaN -0.046050 0.033607 -0.037315 -0.359299 -0.036493 -0.122814 0.169344 0.059969 0.163932 0.055264 1.000000 0.832520 0.117409 -0.078081
Total_Trans_Ct -0.002961 NaN -0.067107 0.052085 -0.049819 -0.241891 -0.042787 -0.152213 0.075927 0.056060 0.070885 0.024123 0.832520 1.000000 0.152392 0.002838
Total_Ct_Chng_Q4_Q1 0.007672 NaN -0.018824 0.010959 -0.018958 0.041464 -0.043911 -0.098280 -0.006200 0.095009 -0.014717 0.366869 0.117409 0.152392 1.000000 0.080625
Avg_Utilization_Ratio 0.000266 NaN 0.007023 -0.037256 -0.007541 0.067663 -0.007503 -0.055471 -0.482965 0.624022 -0.538808 0.036923 -0.078081 0.002838 0.080625 1.000000
In [66]:
fig, ax = plt.subplots(figsize = (24,10))
sns.heatmap(correlation, annot = True, square = True, fmt = '.2f')
plt.show()

Observăm o puternică corelație pozitivă de de 0,83 între Total_Trans_Ct și Total_Trans_Amt. De asemenea, există o puternică corelație de 0,79 între Months_On_Book și Customer_Age. În plus, există o corelație medie pozitivă de 0,62 între Total_Used_Bal și Avg_Utilization_Ratio.

În ceea ce privește corelațiile slabe, putem observa o corelație pozitivă de 0,37 între Total_Trans_Ct și Attrition_Flag, precum și între Total_Ct_Chng_Q4_Q1 și Total_Amt_Chng_Q4_Q1. Există, de asemenea, o corelație slabă de 0,31 între Total_Ct_Chng_Q4_Q1 și Attrition_Flag.

Pe de altă parte, există o corelație slabă negativă de -0,36 între Total_Trans_Amt și Total_Relationship_Count. De asemenea, putem observa o corelație negativă slabă de -0,48 între Credit_Limit și Avg_Utilization_Ratio. Există, de asemenea, o corelație negativă medie de -0,54 între Avg_Utilization_Ratio și Total_Unused_Bal.

Celelalte corelații au un impact scăzut, ceea ce înseamnă că au o relevanță mult mai mică.

D. Clasificare¶

1. Declararea variabilei independente și a variabilei target¶

In [67]:
#variabila target
y = data['Attrition_Flag']

#variabila independenta
X = data.drop(columns = ['Attrition_Flag'])
In [68]:
X.shape, y.shape
Out[68]:
((10127, 20), (10127,))

2. Procesarea datelor¶

În cazul modelului nostru, categoriile cu cea mai mică relevanță sunt Gender și Card_Category, așadar le vom elimina.

In [70]:
X=X.drop(columns=['Gender','Card_Category'])
In [71]:
X.head()
Out[71]:
CLIENTNUM Customer_Age Dependent_count Education_Level Marital_Status Income_Category Months_on_book Total_Relationship_Count Months_Inactive_12_mon Contacts_Count_12_mon Credit_Limit Total_Used_Bal Total_Unused_Bal Total_Amt_Chng_Q4_Q1 Total_Trans_Amt Total_Trans_Ct Total_Ct_Chng_Q4_Q1 Avg_Utilization_Ratio
0 768805383 45.0 3.0 High School Married $60K - $80K 39 5 1 3 12691.0 777 11914.0 1.335 1144.0 42 1.526 0.061
1 818770008 49.0 5.0 Graduate Single Less than $40K 44 6 1 2 8256.0 864 7392.0 1.541 1291.0 33 1.526 0.105
2 713982108 51.0 3.0 Graduate Married $80K - $120K 36 4 1 0 3418.0 0 3418.0 1.543 1887.0 20 1.526 0.000
3 769911858 40.0 4.0 High School Unknown Less than $40K 34 3 4 1 3313.0 2517 796.0 1.405 1171.0 20 1.526 0.760
4 709106358 40.0 3.0 Uneducated Married $60K - $80K 21 5 1 0 4716.0 0 4716.0 1.543 816.0 28 1.526 0.000

c) Categorical coding

In [73]:
categorical_columns = [col for col in X.columns if X[col].dtypes == 'object']
In [74]:
categorical_columns
Out[74]:
['Education_Level', 'Marital_Status', 'Income_Category']
In [75]:
X = pd.get_dummies(X,columns = categorical_columns)
In [76]:
X.head()
Out[76]:
CLIENTNUM Customer_Age Dependent_count Months_on_book Total_Relationship_Count Months_Inactive_12_mon Contacts_Count_12_mon Credit_Limit Total_Used_Bal Total_Unused_Bal ... Marital_Status_Divorced Marital_Status_Married Marital_Status_Single Marital_Status_Unknown Income_Category_$120K + Income_Category_$40K - $60K Income_Category_$60K - $80K Income_Category_$80K - $120K Income_Category_Less than $40K Income_Category_Unknown
0 768805383 45.0 3.0 39 5 1 3 12691.0 777 11914.0 ... 0 1 0 0 0 0 1 0 0 0
1 818770008 49.0 5.0 44 6 1 2 8256.0 864 7392.0 ... 0 0 1 0 0 0 0 0 1 0
2 713982108 51.0 3.0 36 4 1 0 3418.0 0 3418.0 ... 0 1 0 0 0 0 0 1 0 0
3 769911858 40.0 4.0 34 3 4 1 3313.0 2517 796.0 ... 0 0 0 1 0 0 0 0 1 0
4 709106358 40.0 3.0 21 5 1 0 4716.0 0 4716.0 ... 0 1 0 0 0 0 1 0 0 0

5 rows × 32 columns

3. Seturi de antrenare si testare¶

In [77]:
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=10)
In [78]:
X_train.shape, X_test.shape, y_train.shape, y_test.shape
Out[78]:
((8101, 32), (2026, 32), (8101,), (2026,))

Algoritmul Random Forest Classifier¶

In [79]:
rf = RandomForestClassifier(n_estimators = 300, max_depth = 5, n_jobs = -1, random_state = 123)

Începem procesul de antrenare a modelului cu ajutorul algoritmului Random Forest pe setul de date pregătit anterior.

In [80]:
rf.fit(X_train, y_train)
Out[80]:
RandomForestClassifier(max_depth=5, n_estimators=300, n_jobs=-1,
                       random_state=123)
In a Jupyter environment, please rerun this cell to show the HTML representation or trust the notebook.
On GitHub, the HTML representation is unable to render, please try loading this page with nbviewer.org.
RandomForestClassifier(max_depth=5, n_estimators=300, n_jobs=-1,
                       random_state=123)

Prezicerea rezultatelor¶

In [81]:
y_predict = rf.predict(X_test)
In [82]:
y_predict
Out[82]:
array([1, 1, 1, ..., 0, 1, 1], dtype=int64)

Performanta¶

În urma aplicării algoritmului Random Forest, verificăm criteriile de performanță ale modelului nostru. Acest lucru include evaluarea acurateții, preciziei, rapelului și scorului F1. De asemenea, putem examina matricea de confuzie pentru a evalua cât de bine modelul nostru poate distinge între clasele pozitive și negative.

a) Acuratețea

In [83]:
accuracy = accuracy_score(y_test,y_predict)
In [85]:
print('Pentru algoritmul Random Forest Classifier, avem o acuratete de:',accuracy)
Pentru algoritmul Random Forest Classifier, avem o acuratete de: 0.920533070088845

b) Matricea de confuzie

In [86]:
cm = confusion_matrix(y_test,y_predict)
print(cm)
[[ 171  150]
 [  11 1694]]
In [87]:
fig,ax = plt.subplots(figsize = (10, 8))
sns.heatmap(cm,annot = True, fmt = 'd')
plt.show()

Prin analizarea graficului furnizat, putem concluziona că există 1694 valori cu adevărat pozitive și 171 valori cu adevărat negative, dar și 11 valori eronat considerate negative și 150 valori eronat considerate pozitive. În urma acestor constatări, precizia rezultată este de 91,8%, sensibilitatea este de 99,3%, scorul AUC este de 76,3%, iar acuratețea globală este de 92%.

c) Precizie și sensibilitate

In [88]:
precision = precision_score(y_test, y_predict)
recall = recall_score(y_test, y_predict)
print('Precision score:', precision)
print('Recall score:', recall)
Precision score: 0.9186550976138829
Recall score: 0.9935483870967742

Verificam daca exista overfitting

In [90]:
y_train_predict = rf.predict(X_train)
In [91]:
precision = precision_score(y_train, y_train_predict)
recall = recall_score(y_train, y_train_predict)
print('Precision score:', precision)
print('Recall score:', recall)
Precision score: 0.9142003801248981
Recall score: 0.9910228108903606

Interpretarea datelor indică că valorile preciziei și sensibilității s-au modificat într-o măsură neglijabilă. Precizia a scăzut cu aproximativ 0,004, iar sensibilitatea a scăzut cu aproximativ 0,002. Acest lucru sugerează că nu există un overfitting semnificativ care să afecteze rezultatele obținute.

d) Scorul AUC

In [92]:
auc_score = roc_auc_score(y_test, y_predict)
print('AUC score is:', auc_score)
AUC score is: 0.763129333735303

Verificam daca exista overfitting

In [107]:
y_predict_train = rf.predict(X_train)
auc_score = roc_auc_score(y_train, y_predict_train)
print('AUC score is:', auc_score)
AUC score is: 0.7535512216779522

Diferența dintre setul de testare și cel de antrenare este prea mică pentru a avea un overfitting semnificativ care să influențeze rezultatele.

In [93]:
fpr,tpr,treshold = roc_curve(y_test, y_predict)
plt.plot(fpr,tpr)
Out[93]:
[<matplotlib.lines.Line2D at 0x18c596a7220>]

Conform interpretării graficului, acesta ilustrează o curbă ROC ce reflectă performanța unui model de clasificare. Se poate observa că între 0.0 și 0.5, curba crește într-un mod specific funcției f(x) = 2*x, iar între 0.5 și 1, valoarea curbei rămâne constantă la 1.

Algoritmul XGBoost (eXtreme Gradient Boosting)¶

In [94]:
xgb = XGBClassifier(n_estimators=250, max_depth=4, n_jobs=-1, random_state=100)
In [95]:
xgb.fit(X_train, y_train)
Out[95]:
XGBClassifier(base_score=None, booster=None, callbacks=None,
              colsample_bylevel=None, colsample_bynode=None,
              colsample_bytree=None, early_stopping_rounds=None,
              enable_categorical=False, eval_metric=None, feature_types=None,
              gamma=None, gpu_id=None, grow_policy=None, importance_type=None,
              interaction_constraints=None, learning_rate=None, max_bin=None,
              max_cat_threshold=None, max_cat_to_onehot=None,
              max_delta_step=None, max_depth=4, max_leaves=None,
              min_child_weight=None, missing=nan, monotone_constraints=None,
              n_estimators=250, n_jobs=-1, num_parallel_tree=None,
              predictor=None, random_state=100, ...)
In a Jupyter environment, please rerun this cell to show the HTML representation or trust the notebook.
On GitHub, the HTML representation is unable to render, please try loading this page with nbviewer.org.
XGBClassifier(base_score=None, booster=None, callbacks=None,
              colsample_bylevel=None, colsample_bynode=None,
              colsample_bytree=None, early_stopping_rounds=None,
              enable_categorical=False, eval_metric=None, feature_types=None,
              gamma=None, gpu_id=None, grow_policy=None, importance_type=None,
              interaction_constraints=None, learning_rate=None, max_bin=None,
              max_cat_threshold=None, max_cat_to_onehot=None,
              max_delta_step=None, max_depth=4, max_leaves=None,
              min_child_weight=None, missing=nan, monotone_constraints=None,
              n_estimators=250, n_jobs=-1, num_parallel_tree=None,
              predictor=None, random_state=100, ...)

Prezicerea rezultatelor¶

In [96]:
y_predict = xgb.predict(X_test)
y_predict
Out[96]:
array([1, 1, 1, ..., 0, 1, 1])

Performanta¶

a) Acuratețea

In [98]:
accuracy = accuracy_score(y_test, y_predict)
print('Accuracy for XGBoost is:', accuracy)
Accuracy for XGBoost is: 0.9693978282329714

b) Matricea de confuzie

In [99]:
cm = confusion_matrix(y_test, y_predict)
print(cm)
[[ 280   41]
 [  21 1684]]
In [100]:
fig, ax = plt.subplots(figsize = (11, 8))
sns.heatmap(cm, annot = True, fmt='d')
plt.show()

Bazându-ne pe analiza și graficul furnizate, observăm că avem 1684 valori cu adevărat pozitive și 280 valori cu adevărat negative, împreună cu 21 de valori fals negative și 41 valori fals pozitive. Prin urmare, putem concluziona că acuratețea este de 96,9%, precizia este de 97,6%, sensibilitatea este de 98,7%, iar scorul AUC este de 92,9%.

c) Precizie și sensibilitate

In [101]:
precision = precision_score(y_test, y_predict)
recall = recall_score(y_test, y_predict)
print('Precision score:', precision)
print('Recall score:', recall)
Precision score: 0.976231884057971
Recall score: 0.987683284457478

Verificam daca exista overfitting

In [102]:
y_train_predict = rf.predict(X_train)
precision = precision_score(y_train, y_train_predict)
recall = recall_score(y_train, y_train_predict)
print('Precision score:', precision)
print('Recall score:', recall)
Precision score: 0.9142003801248981
Recall score: 0.9910228108903606

Scorul de sensibilitate s-a modificat cu o valoare neglijabilă de aproximativ 0,035, sugestiv pentru absența unui overfitting sau underfitting relevant. În schimb, scorul de precizie a scăzut cu o valoare semnificativă de peste 0,6, ceea ce sugerează existența unui underfitting important.

d) Scorul AUC

In [103]:
auc_score = roc_auc_score(y_test, y_predict)
print('AUC score', auc_score)
AUC score 0.9299787138798294

Verificam daca exista overfitting

In [105]:
y_predict_train = xgb.predict(X_train)
auc_score = roc_auc_score(y_train, y_predict_train)
print('AUC score is:', auc_score)
AUC score is: 1.0

Se pare că există un overfitting în ceea ce privește scorul AUC, având în vedere că diferența dintre rezultatele de la antrenare și cele de la testare este de aproximativ 0,7. Scorul AUC obținut la antrenare a atins chiar valoarea maximă de 1, ceea ce sugerează o posibilă adaptare excesivă a modelului la datele de antrenare.

Prelucrarea hipervalorilor¶

a) Vom identifica acum posibile hipervalori pentru algoritmul XGBoost.

In [109]:
n_estimators = [150,250]
max_depth = [3,4]
learning_rate = [0.1,0.05]

b) Cautam hipervalorile

In [110]:
results = []
for est in n_estimators:
    for md in max_depth:
        for lr in learning_rate:
            xgb = XGBClassifier(n_estimators = est, max_depth = md, learning_rate = lr, n_jobs = -1, random_state = 100, subsample = 0.7, colsample_bytree = 0.5)
            xgb.fit(X_train, y_train)
            y_predict = xgb.predict(X_test)
            auc_score = roc_auc_score(y_test,y_predict)
            results.append(['estimators', est, 'max_depth', md, 'leraning_rate', lr, 'auc', auc_score])
In [111]:
print(results)
[['estimators', 200, 'max_depth', 3, 'leraning_rate', 0.1, 'auc', 0.9262769388183918], ['estimators', 200, 'max_depth', 3, 'leraning_rate', 0.05, 'auc', 0.91498890015622], ['estimators', 200, 'max_depth', 4, 'leraning_rate', 0.1, 'auc', 0.930565224143759], ['estimators', 200, 'max_depth', 4, 'leraning_rate', 0.05, 'auc', 0.9138158796283607], ['estimators', 300, 'max_depth', 3, 'leraning_rate', 0.1, 'auc', 0.9268634490823215], ['estimators', 300, 'max_depth', 3, 'leraning_rate', 0.05, 'auc', 0.9146956450242553], ['estimators', 300, 'max_depth', 4, 'leraning_rate', 0.1, 'auc', 0.9284210814810755], ['estimators', 300, 'max_depth', 4, 'leraning_rate', 0.05, 'auc', 0.9196617973524817]]
In [113]:
results
Out[113]:
[['estimators',
  200,
  'max_depth',
  3,
  'leraning_rate',
  0.1,
  'auc',
  0.9262769388183918],
 ['estimators',
  200,
  'max_depth',
  3,
  'leraning_rate',
  0.05,
  'auc',
  0.91498890015622],
 ['estimators',
  200,
  'max_depth',
  4,
  'leraning_rate',
  0.1,
  'auc',
  0.930565224143759],
 ['estimators',
  200,
  'max_depth',
  4,
  'leraning_rate',
  0.05,
  'auc',
  0.9138158796283607],
 ['estimators',
  300,
  'max_depth',
  3,
  'leraning_rate',
  0.1,
  'auc',
  0.9268634490823215],
 ['estimators',
  300,
  'max_depth',
  3,
  'leraning_rate',
  0.05,
  'auc',
  0.9146956450242553],
 ['estimators',
  300,
  'max_depth',
  4,
  'leraning_rate',
  0.1,
  'auc',
  0.9284210814810755],
 ['estimators',
  300,
  'max_depth',
  4,
  'leraning_rate',
  0.05,
  'auc',
  0.9196617973524817]]

Modelul cu parametrii n_estimators=200, max_depth=4 și learning_rate=0.1 este cel mai performant.

Verificam daca exista overfitting

In [114]:
best_model = XGBClassifier(n_estimator = 200, max_depth = 4, rate = 0.1, n_jobs = -1, subsample = 0.6, solsample_bytree = 0.5, random_state = 123)
best_model.fit(X_train, y_train)
y_predict_train = best_model.predict(X_train)
y_predict_test = best_model.predict(X_test)
auc_score_train = roc_auc_score(y_train, y_predict_train)
auc_score_test = roc_auc_score(y_test, y_predict_test)
print('AUC train', auc_score_train)
print('AUC test', auc_score_test)
[14:24:41] WARNING: C:\buildkite-agent\builds\buildkite-windows-cpu-autoscaling-group-i-07593ffd91cd9da33-1\xgboost\xgboost-ci-windows\src\learner.cc:767: 
Parameters: { "n_estimator", "rate", "solsample_bytree" } are not used.

AUC train 0.9941986214077326
AUC test 0.9175176546897982

Diferența dintre scorul AUC obținut pentru setul de antrenare și cel obținut pentru setul de testare este de aproximativ 0,079, ceea ce indică existența unui overfitting.

Salvarea rezultatelor intr-un fisier binar prin libraria Pickle¶

In [115]:
with open('C:\\Users\\valen\\Desktop\\data\\final_model.pkl','wb') as file:
    pickle.dump(best_model, file)

E. Analiza indicatorilor lift și gain¶

Scopul acestei analize este de a evalua eficacitatea unui model predictiv.

In [117]:
y_predict_proba = xgb.predict_proba(X_test)
y_predict_proba
Out[117]:
array([[3.4443736e-02, 9.6555626e-01],
       [3.8262010e-03, 9.9617380e-01],
       [2.5113821e-03, 9.9748862e-01],
       ...,
       [9.8464829e-01, 1.5351691e-02],
       [9.6988678e-04, 9.9903011e-01],
       [4.4490695e-02, 9.5550931e-01]], dtype=float32)

a) Calculăm probabilitățile.

In [118]:
y_predict_proba_class_1 = y_predict_proba[:, 1]
y_predict_proba_class_1
Out[118]:
array([0.96555626, 0.9961738 , 0.9974886 , ..., 0.01535169, 0.9990301 ,
       0.9555093 ], dtype=float32)

b) Scorul AUC

In [120]:
auc_score = roc_auc_score(y_test, y_predict_proba_class_1)
In [121]:
print(auc_score)
0.9931263189629183
In [136]:
# vom crea un nou DataFrame gol
lift_gain_report = pd.DataFrame()

#il vom introduce pe y_test in DataFrame
lift_gain_report['y_test'] = y_test

#vom adăuga probabilitățile în DataFrame
lift_gain_report['Predicted Probabilities'] = y_predict_proba_class_1

#vom ordona probabilitățile descrescător
lift_gain_report['Probabilities Rank'] = lift_gain_report['Predicted Probabilities'].rank(method='first',ascending=False,pct=True)

#vom calcula o coloană de decili pentru a separa observațiile
lift_gain_report['Decil group']=np.floor((1- lift_gain_report['Probabilities Rank'])*10)+1

#grupăm observațiile
lift_gain_report['No of observations']=1
lift_gain_report=lift_gain_report.groupby(['Decil group']).sum().reset_index()

#introducem numărul cumulativ de observații
lift_gain_report['Cumulative no of observations']=lift_gain_report['No of observations'].cumsum()

#procentul cumulativ
lift_gain_report['Cumulative % of observations']=lift_gain_report['Cumulative no of observations']/lift_gain_report['Cumulative no of observations'].max()

#vom calcula cumulativele pozitive
lift_gain_report['Cumulative no of positives']=lift_gain_report['y_test'].cumsum()

#vom calcula cumulativele pozitive(Gain)
lift_gain_report['Gain']=lift_gain_report['Cumulative no of positives']/lift_gain_report['Cumulative no of positives'].max()

#vom calcula valorile Lift
lift_gain_report['Lift']=lift_gain_report['Gain']/lift_gain_report['Cumulative % of observations']

lift_gain_report
Out[136]:
Decil group y_test Predicted Probabilities Probabilities Rank No of observations Cumulative no of observations Cumulative % of observations Cumulative no of positives Gain Lift
0 1.0 3 6.797051 192.880059 203 203 0.100197 3 0.001760 0.017561
1 2.0 93 107.519356 172.539980 203 406 0.200395 96 0.056305 0.280970
2 3.0 191 188.822098 151.500000 202 608 0.300099 287 0.168328 0.560910
3 4.0 203 199.250183 131.960020 203 811 0.400296 490 0.287390 0.717944
4 5.0 202 200.446136 111.119941 202 1013 0.500000 692 0.405865 0.811730
5 6.0 203 202.192703 91.380059 203 1216 0.600197 895 0.524927 0.874590
6 7.0 203 202.560715 71.039980 203 1419 0.700395 1098 0.643988 0.919465
7 8.0 202 201.749695 50.500000 202 1621 0.800099 1300 0.762463 0.952962
8 9.0 203 202.841263 30.460020 203 1824 0.900296 1503 0.881525 0.979150
9 10.0 202 201.917343 10.119941 202 2026 1.000000 1705 1.000000 1.000000

Vom crea graficele pentru indicatorii Gain și Lift¶

a) Graficul Lift

In [129]:
fig,ax=plt.subplots(figsize=(15,9))
barplot=plt.bar(lift_gain_report['Decil group'],lift_gain_report['Lift'])
plt.title('Lift bar plot')
plt.xlabel('Decil group')
plt.ylabel('Lift')
plt.xticks(lift_gain_report['Decil group'])

for b in barplot:
    plt.text(b.get_x()+b.get_width()/2,b.get_height()+0.1,round(b.get_height(),2),ha='center')
    
plt.show()

Din analiza graficului pentru indicatorul Lift, se poate observa că pentru 7 din cele 10 grupuri Decile, valoarea indicatorului este de 1,19. Pentru grupul Decil 8, valoarea Lift este de 1,18, pentru Decil 9 este de 1,11, iar pentru Decil 10 este de 1.

b) Graficul Gain

In [162]:
lift_gain_report['Random Selection']=lift_gain_report['Decil group']/lift_gain_report['Decil group'].max()
In [163]:
fig,ax=plt.subplots(figsize=(14,8))
sns.lineplot(data=lift_gain_report,x=lift_gain_report['Decil group'],y=lift_gain_report['Gain'])
sns.lineplot(data=lift_gain_report,x=lift_gain_report['Decil group'],y=lift_gain_report['Random Selection'])
plt.title('Gain plot')
plt.xticks(lift_gain_report['Decil group'])
plt.yticks(round(lift_gain_report['Gain'],2))
Out[163]:
([<matplotlib.axis.YTick at 0x18c6af534c0>,
  <matplotlib.axis.YTick at 0x18c6af52e60>,
  <matplotlib.axis.YTick at 0x18c647a1540>,
  <matplotlib.axis.YTick at 0x18c647a2200>,
  <matplotlib.axis.YTick at 0x18c647a22f0>,
  <matplotlib.axis.YTick at 0x18c647a33a0>,
  <matplotlib.axis.YTick at 0x18c647a2e30>,
  <matplotlib.axis.YTick at 0x18c647a0250>,
  <matplotlib.axis.YTick at 0x18c6af523b0>,
  <matplotlib.axis.YTick at 0x18c647c53c0>],
 [Text(0, 0.0, '0.00'),
  Text(0, 0.06, '0.06'),
  Text(0, 0.17, '0.17'),
  Text(0, 0.29, '0.29'),
  Text(0, 0.41, '0.41'),
  Text(0, 0.52, '0.52'),
  Text(0, 0.64, '0.64'),
  Text(0, 0.76, '0.76'),
  Text(0, 0.88, '0.88'),
  Text(0, 1.0, '1.00')])

Se poate observa cum valoarea indicatorului Gain variază pentru fiecare punct din grupul Decil. Pentru primul punct va fi egal cu 0, pentru al doilea punct va fi egal cu 0,06, pentru al treilea punct va fi egal cu 0,17, pentru al patrulea punct va fi egal cu 0,29, pentru al cincilea punct va fi egal cu 0,41, pentru al șaselea punct va fi egal cu 0,52, pentru al șaptelea punct va fi egal cu 0,64, pentru al optulea punct va fi egal cu 0,76, pentru al nouălea punct va fi egal cu 0,88, iar pentru al zecelea punct valoarea va ajunge la 1.

Importanța caracteristicilor¶

In [134]:
feat_imp=xgb.get_booster().get_score(importance_type='total_gain')
feat_imp
Out[134]:
{'CLIENTNUM': 337.0828552246094,
 'Customer_Age': 1529.3056640625,
 'Dependent_count': 132.06858825683594,
 'Months_on_book': 615.2696533203125,
 'Total_Relationship_Count': 3694.20458984375,
 'Months_Inactive_12_mon': 1212.135986328125,
 'Contacts_Count_12_mon': 903.0044555664062,
 'Credit_Limit': 815.6828002929688,
 'Total_Used_Bal': 5177.43310546875,
 'Total_Unused_Bal': 625.514892578125,
 'Total_Amt_Chng_Q4_Q1': 2801.148193359375,
 'Total_Trans_Amt': 11249.3623046875,
 'Total_Trans_Ct': 11149.4453125,
 'Total_Ct_Chng_Q4_Q1': 4541.037109375,
 'Avg_Utilization_Ratio': 1186.154296875,
 'Education_Level_College': 10.916389465332031,
 'Education_Level_Doctorate': 3.8043665885925293,
 'Education_Level_Graduate': 10.775384902954102,
 'Education_Level_High School': 10.846713066101074,
 'Education_Level_Post-Graduate': 5.802804946899414,
 'Education_Level_Uneducated': 11.02813720703125,
 'Education_Level_Unknown': 8.50363540649414,
 'Marital_Status_Divorced': 9.740646362304688,
 'Marital_Status_Married': 187.2605743408203,
 'Marital_Status_Single': 52.36482620239258,
 'Marital_Status_Unknown': 12.8047513961792,
 'Income_Category_$120K +': 24.90848159790039,
 'Income_Category_$40K - $60K': 1.5076053142547607,
 'Income_Category_$60K - $80K': 64.86402130126953,
 'Income_Category_$80K - $120K': 8.905508995056152,
 'Income_Category_Less than $40K': 28.50957489013672,
 'Income_Category_Unknown': 5.852596282958984}
In [135]:
feature_importance=pd.DataFrame()
feature_importance['Variable']=feat_imp.keys()
feature_importance['Importance Value']=feat_imp.values()
feature_importance['%Importance Feature']=feature_importance['Importance Value']/feature_importance['Importance Value'].sum()*100
feature_importance.sort_values(by=['Importance Value'],ascending=True)
Out[135]:
Variable Importance Value %Importance Feature
27 Income_Category_$40K - $60K 1.507605 0.003247
16 Education_Level_Doctorate 3.804367 0.008194
19 Education_Level_Post-Graduate 5.802805 0.012499
31 Income_Category_Unknown 5.852596 0.012606
21 Education_Level_Unknown 8.503635 0.018316
29 Income_Category_$80K - $120K 8.905509 0.019182
22 Marital_Status_Divorced 9.740646 0.020980
17 Education_Level_Graduate 10.775385 0.023209
18 Education_Level_High School 10.846713 0.023363
15 Education_Level_College 10.916389 0.023513
20 Education_Level_Uneducated 11.028137 0.023754
25 Marital_Status_Unknown 12.804751 0.027580
26 Income_Category_$120K + 24.908482 0.053651
30 Income_Category_Less than $40K 28.509575 0.061407
24 Marital_Status_Single 52.364826 0.112789
28 Income_Category_$60K - $80K 64.864021 0.139711
2 Dependent_count 132.068588 0.284464
23 Marital_Status_Married 187.260574 0.403342
0 CLIENTNUM 337.082855 0.726045
3 Months_on_book 615.269653 1.325234
9 Total_Unused_Bal 625.514893 1.347301
7 Credit_Limit 815.682800 1.756905
6 Contacts_Count_12_mon 903.004456 1.944988
14 Avg_Utilization_Ratio 1186.154297 2.554867
5 Months_Inactive_12_mon 1212.135986 2.610829
1 Customer_Age 1529.305664 3.293983
10 Total_Amt_Chng_Q4_Q1 2801.148193 6.033415
4 Total_Relationship_Count 3694.204590 7.956976
13 Total_Ct_Chng_Q4_Q1 4541.037109 9.780975
8 Total_Used_Bal 5177.433105 11.151713
12 Total_Trans_Ct 11149.445312 24.014876
11 Total_Trans_Amt 11249.362305 24.230088

Vizualizarea importanței caracteristicilor prin graficele SHAP¶

In [142]:
explainer=shap.TreeExplainer(xgb)
shap_values=explainer.shap_values(X_test)
shap_values
Out[142]:
array([[ 0.04779819, -0.16809815, -0.02426933, ...,  0.00058599,
         0.01383665, -0.0003608 ],
       [ 0.04676734, -0.11945987, -0.04687361, ..., -0.00479998,
         0.01643804, -0.00041905],
       [ 0.14659846, -0.10013846, -0.01258962, ...,  0.00660091,
         0.01569512, -0.0005992 ],
       ...,
       [ 0.08655098, -0.23870268, -0.02981216, ..., -0.00320706,
         0.00855534,  0.0046726 ],
       [ 0.02689816, -0.06554399, -0.02622731, ...,  0.00042218,
         0.02002614, -0.00062051],
       [-0.01122317, -0.10594083, -0.02742577, ...,  0.00455419,
         0.02909476,  0.00176551]], dtype=float32)
In [144]:
shap.summary_plot(shap_values,X_test,plot_size=(22,11))
In [145]:
index=123
shap.initjs()
shap.force_plot(explainer.expected_value, shap_values[index],X_test.iloc[index])
Out[145]:
Visualization omitted, Javascript library not loaded!
Have you run `initjs()` in this notebook? If this notebook was from another user you must also trust this notebook (File -> Trust notebook). If you are viewing this notebook on github the Javascript has been stripped for security. If you are using JupyterLab this error is because a JupyterLab extension has not yet been written.
In [146]:
index=79
shap.initjs()
shap.force_plot(explainer.expected_value, shap_values[index],X_test.iloc[index])
Out[146]:
Visualization omitted, Javascript library not loaded!
Have you run `initjs()` in this notebook? If this notebook was from another user you must also trust this notebook (File -> Trust notebook). If you are viewing this notebook on github the Javascript has been stripped for security. If you are using JupyterLab this error is because a JupyterLab extension has not yet been written.
In [147]:
index=100
shap.initjs()
shap.force_plot(explainer.expected_value, shap_values[index],X_test.iloc[index])
Out[147]:
Visualization omitted, Javascript library not loaded!
Have you run `initjs()` in this notebook? If this notebook was from another user you must also trust this notebook (File -> Trust notebook). If you are viewing this notebook on github the Javascript has been stripped for security. If you are using JupyterLab this error is because a JupyterLab extension has not yet been written.
In [148]:
index=219
shap.initjs()
shap.force_plot(explainer.expected_value, shap_values[index],X_test.iloc[index])
Out[148]:
Visualization omitted, Javascript library not loaded!
Have you run `initjs()` in this notebook? If this notebook was from another user you must also trust this notebook (File -> Trust notebook). If you are viewing this notebook on github the Javascript has been stripped for security. If you are using JupyterLab this error is because a JupyterLab extension has not yet been written.
In [149]:
index=17
shap.initjs()
shap.force_plot(explainer.expected_value, shap_values[index],X_test.iloc[index])
Out[149]:
Visualization omitted, Javascript library not loaded!
Have you run `initjs()` in this notebook? If this notebook was from another user you must also trust this notebook (File -> Trust notebook). If you are viewing this notebook on github the Javascript has been stripped for security. If you are using JupyterLab this error is because a JupyterLab extension has not yet been written.

Acuratețea probei

In [150]:
pr=pd.DataFrame()
pr['proba']=y_predict_proba_class_1
pr[pr['proba']==pr['proba'].max()]
Out[150]:
proba
1070 0.999916

Avem o acuratețe a rezultatelor de peste 99%.

F. Salvarea finală a datelor¶

In [151]:
data.to_csv('C:\\Users\\valen\\Desktop\\data\\dataset.csv')
In [158]:
with open('C:\\Users\\valen\\Desktop\\data\\final_model.pkl','wb') as file:
    pickle.dump(best_model,file)